package com.good.shopping.product.redis;

import com.alibaba.fastjson.JSON;
import com.alibaba.fastjson.TypeReference;
import com.good.shopping.product.vo.Catelog2Vo;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.data.redis.core.StringRedisTemplate;
import org.springframework.data.redis.core.script.DefaultRedisScript;
import org.springframework.util.StringUtils;

import java.util.Arrays;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.TimeUnit;

/**
 * 2.使用分布式锁
 *   原理：
 *      占分布式锁：去redis占锁
 *      保证只有一个线程去查询数据库，其他都等待状态，进行自旋锁
 *      加锁的时候设置过期时间（否则，执行业务代码，等待时间久，出现异常，发生断电现象，发生死锁状态）
 *      加锁、解决需要使用原子性操作
 *      （否则会引起，多线程下，第一线程上锁，等待时间久，然后过期锁执行，
 *        第二个线程进来，进行加锁，这个时候第一个线程执行完业务代码，再执行 finally释放锁，
 *        第一个线程释放的是第二个线程的锁，之后第三个线程再进来，就混乱了，所以需要原子性操作）
 *    redis4 使用分布式锁
 * @author shengren.yan
 * @create 2022-02-23
 */
public class redis2 {

    // redis
    @Autowired
    StringRedisTemplate stringRedisTemplate;

    /**
     * 从数据库查询并封装数据::分布式锁
     * @return
     */
    public Map<String, List<Catelog2Vo>> getCatalogJsonFromDbWithRedisLock() {

        //1、占分布式锁。去redis占坑    设置过期时间必须和加锁是同步的，保证原子性（避免死锁）
        String uuid = UUID.randomUUID().toString();
        Boolean lock = stringRedisTemplate.opsForValue().setIfAbsent("lock", uuid,300, TimeUnit.SECONDS);
        if (lock) {
            System.out.println("获取分布式锁成功...");
            Map<String, List<Catelog2Vo>> dataFromDb = null;
            try {
                //加锁成功...执行业务
                dataFromDb = getDataFromDb();
            } finally {
                String script = "if redis.call('get', KEYS[1]) == ARGV[1] then return redis.call('del', KEYS[1]) else return 0 end";
                //删除锁  原子类操作
                stringRedisTemplate.execute(new DefaultRedisScript<Long>(script, Long.class), Arrays.asList("lock"), uuid);

            }
            //先去redis查询下保证当前的锁是自己的
            //获取值对比，对比成功删除=原子性 lua脚本解锁
            // String lockValue = stringRedisTemplate.opsForValue().get("lock");
            // if (uuid.equals(lockValue)) {
            //     //删除我自己的锁
            //     stringRedisTemplate.delete("lock");
            // }

            return dataFromDb;
        } else {
            System.out.println("获取分布式锁失败...等待重试...");
            //加锁失败...重试机制
            //休眠一百毫秒
            try { TimeUnit.MILLISECONDS.sleep(100); } catch (InterruptedException e) { e.printStackTrace(); }

            return getCatalogJsonFromDbWithRedisLock();     //自旋的方式
        }
    }


    // 查询数据库
    private Map<String, List<Catelog2Vo>> getDataFromDb() {
        //得到锁以后，我们应该再去缓存中确定一次，如果没有才需要继续查询  catalogJson 缓存数据
        String catalogJson = stringRedisTemplate.opsForValue().get("catalogJson");
        if (!StringUtils.isEmpty(catalogJson)) {
            //缓存不为空直接返回
            Map<String, List<Catelog2Vo>> result = JSON.parseObject(catalogJson, new TypeReference<Map<String, List<Catelog2Vo>>>() {

            });
            return result;
        }

        System.out.println("查询了数据库");

        //1、查出所有分类

        // 封装数据 -- 模拟查询数据库
        Map<String, List<Catelog2Vo>> parentCid = null;

        //3、将查到的数据放入缓存,将对象转为json
        String valueJson = JSON.toJSONString(parentCid);
        stringRedisTemplate.opsForValue().set("catalogJson", valueJson, 1, TimeUnit.DAYS);

        return parentCid;
    }

}
